#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/*
 * $ tcc -run - <<<"main(){printf(\"%*s\r%s\n$ \", $COLUMNS, \"Hello\", \"PS1\");}"
 */

/*
 * http://wiki.archlinux.org/index.php/Bash/Prompt_customization
 */

/*
 * http://bugs.python.org/issue20359
 * http://stackoverflow.com/questions/9468435/look-how-to-fix-column-calculation-in-python-readline-if-use-color-prompt
 * http://unix.stackexchange.com/questions/367485/using-echo-e-in-ps1-causes-line-break-issues-in-shell
 */
/* From readline.h: */
#define RL_PROMPT_START_IGNORE  '\001'
#define RL_PROMPT_END_IGNORE    '\002'

#ifdef LIBGIT
#include <git2.h>

static char gitvar_oid_str[GIT_OID_HEXSZ+1];
static char *gitvar_branch_name;
static char *gitvar_commit_summary;

static const char * const git_head_detached = "HEAD detached at";

static void
git_print_error(void)
{
	const git_error *e = giterr_last();
	fprintf(stderr, "libgit error: %s\n", e->message);
/* 	fprintf(stderr, "libgit error: %d/%d: %s\n", ret, e->klass, e->message);
 */
}

static int
git_init_vars(void)
{
	git_repository *repo;
	git_reference *ref;
	git_oid oid;
	git_commit *commit;
	int ret;

	git_libgit2_init();

	ret = git_repository_open_ext(&repo, ".", 0, NULL);
	if (ret) {
		git_print_error();
		goto skip_libgit;
	}

	/* repo head */
	ret = git_repository_head(&ref, repo);
	if (ret) {
		git_print_error();
	}

	/* reference oid */
	ret = git_reference_name_to_id(&oid, repo, git_reference_name(ref));
	if (ret) {
		git_print_error();
	}

	/* oid to str */
	git_oid_tostr(&gitvar_oid_str, sizeof(gitvar_oid_str), &oid);

	/* commit */
	ret = git_commit_lookup(&commit, repo, &oid);
	if (ret) {
		git_print_error();
	} else {
		size_t commit_summary_len;

		commit_summary_len = strlen(git_commit_summary(commit));
		gitvar_commit_summary = calloc(commit_summary_len + 1, sizeof(char));
		if (!gitvar_commit_summary) {
			perror("gitvar_commit_summary");
		} else {
			strncpy(gitvar_commit_summary, git_commit_summary(commit), commit_summary_len);
		}
	}


	if (git_reference_is_branch(ref)) {
		char *branch_name;
		size_t branch_name_len;
		char *branch_upstream;

		/* branch name */
		ret = git_branch_name(&branch_name, ref);
		if (ret) {
			git_print_error();
		} else {
			branch_name_len = strlen(branch_name);
			gitvar_branch_name = calloc(branch_name_len + 1, sizeof(char));
			if (!gitvar_branch_name) {
				perror("gitvar_branch_name");
			} else {
				strncpy(gitvar_branch_name, branch_name, branch_name_len);
			}
		}

		/* branch upstream */
		ret = git_branch_upstream(&branch_upstream, ref);
		if (ret) {
			git_print_error();
		}
	} else {
		gitvar_branch_name = git_head_detached;
#if 0
		if (git_reference_is_remote(ref)) {
			fputs("ref is remote\n", stderr);
		}
		if (git_reference_is_tag(ref)) {
			fputs("ref is tag\n", stderr);
		}
		if (git_reference_is_note(ref)) {
			fputs("ref is note\n", stderr);
		}
#endif
	}

	git_commit_free(commit);
	git_reference_free(ref);
	git_repository_free(repo);
	return 1;
skip_libgit:
	git_libgit2_shutdown();
	return 0;
}
#endif /* LIBGIT */

/*
 * http://en.wikipedia.org/wiki/ANSI_escape_code
 * http://www.linuxjournal.com/article/8603
 */
/* From terminfo(5): */
enum {
	COLOR_BLACK = 0,
	COLOR_RED,
	COLOR_GREEN,
	COLOR_YELLOW,
	COLOR_BLUE,
	COLOR_MAGENTA,
	COLOR_CYAN,
	COLOR_WHITE
};

/* Set foreground color using ANSI escape code */
static void
setaf(int color)
{
	putchar(RL_PROMPT_START_IGNORE);
	printf("\033[0;3%dm", color);
	putchar(RL_PROMPT_END_IGNORE);
}

/* Reset colors */
static void
sgr0(void)
{
	putchar(RL_PROMPT_START_IGNORE);
	printf("\033[0m");
	putchar(RL_PROMPT_END_IGNORE);
}

static int
cprintf(int color, const char *fmt, ...)
{
	va_list ap;
	int ret;

	setaf(color);

	va_start(ap, fmt);
	ret = vprintf(fmt, ap);
	va_end(ap);

	sgr0();

	return ret;
}

static char *
get_homedir(char *homedir, size_t homedir_size)
{
	struct passwd *pwd;

	errno = 0;
	pwd = getpwuid(getuid());
	if (errno) {
		perror("getpwuid");
		return NULL;
	}
	strncpy(homedir, pwd->pw_dir, homedir_size);

	endpwent();

	return homedir;
}

/*
 * s/^$HOME/~/ $PWD
 */
static char *
tilde_home(char *cwd, const char *homedir)
{
	size_t homedir_len = strlen(homedir);
	size_t i;

	if (strncmp(homedir, cwd, homedir_len)) {
		/* not in your own $HOME */
		return NULL;
	}

	cwd[0] = '~';
	for (i=1; cwd[homedir_len-1+i]; ++i) {
		cwd[i] = cwd[homedir_len-1+i];
	}
	cwd[i] = '\0';

	return cwd;
}

int
main(void)
{
	char cwd[PATH_MAX];
	const size_t cwd_size = PATH_MAX;
	char homedir[PATH_MAX];
	const size_t homedir_size = PATH_MAX;
	char *uname;
	char hostname[HOST_NAME_MAX+1];
	const size_t hostname_size = HOST_NAME_MAX+1; /* + '\0' */

	int ret;

	if (!getcwd(cwd, cwd_size)) {
		perror("getcwd");
	}
	if (!get_homedir(homedir, homedir_size)) {
		fputs("get_homedir error\n", stderr);
		exit(EXIT_FAILURE);
	}
	tilde_home(cwd, homedir);

	if (!(uname = getlogin())) {
		perror("getlogin");
	}

	if (gethostname(hostname, hostname_size)) {
		fputs("Couldn't get hostname.\n", stderr);
		exit(EXIT_FAILURE);
	}

#ifdef LIBGIT
	if (git_init_vars()) {
		ret = printf("[%s@%s %s]\n", uname, hostname, cwd);
		fprintf(stderr, "%d\n", ret);
		if (gitvar_branch_name == git_head_detached) {
			cprintf(COLOR_RED, "%s ", git_head_detached);
		} else {
			printf("%s ", gitvar_branch_name);
		}
		cprintf(COLOR_YELLOW, "%s ", gitvar_oid_str);
		printf("%s\n", gitvar_commit_summary);
		ret = cprintf(COLOR_GREEN, "$ ");
		fprintf(stderr, "%d\n", ret);
	} else {
#endif
		printf("[%s@%s %s]\n", uname, hostname, cwd);
		cprintf(COLOR_GREEN, "$ ");
#ifdef LIBGIT
	}
#endif

	return 0;
}
